package edu.hawaii.myisern.model;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import edu.hawaii.myisern.collaborations.jaxb.Collaboration;
import edu.hawaii.myisern.organizations.jaxb.Organization;
import edu.hawaii.myisern.researchers.jaxb.Researcher;

/**
 * Performs semantic checks on lists of Collaboration, Researcher, and Organization data.
 * 
 * @author Andrew Wong, Laura Matsuo
 *
 */
public class XmlSemanticChecker {
  private List<Collaboration> collaborationList;
  private List<Researcher> researcherList;
  private List<Organization> organizationList;

  private List<String> errorList;
  
  boolean verboseMode;

  /**
   * Sets mode of operation.  <p>
   * false = all invalid entries are removed<br>
   * true = no entries are removed, but warning messages are shown
   * 
   * @param newMode Mode of operation.
   */
  public void setMode(boolean newMode) {
    verboseMode = newMode;
  }
  
  /**
   * Listing of UniqueID types.
   */
  enum idType { idCollaboration, idResearcher, idOrganization };
  
  /**
   * Constructor.  Initializes list of id values.
   */
  public XmlSemanticChecker () {
    uniqueIdList = new TreeMap<String, idType>();
    verboseMode = true;
    
    errorList = new ArrayList<String>();
  }
  
  /**
   * Sets the collaboration list that is checked.
   * 
   * @param list New collaboration list.
   */
  public void setCollaborationList(List<Collaboration> list) {
    collaborationList = list;
  }

  /**
   * Sets the researcher list that is checked.
   * 
   * @param list New researcher list.
   */
  public void setResearcherList(List<Researcher> list) {
    researcherList = list;
  }

  /**
   * Sets the organization list that is checked.
   * 
   * @param list New organization list.
   */
  public void setOrganizationList(List<Organization> list) {
    organizationList = list;
  }

  /**
   * Retrieves the Collaboration list.
   * 
   * @return List of collaborations.
   */
  public List<Collaboration> getCollaborationList() {
    return collaborationList;
  }

  /**
   * Retrieves the Organization list.
   * 
   * @return List of organizations.
   */
  public List<Organization> getOrganizationList() {
    return organizationList;
  }

  /**
   * Retrieves the Researcher list.
   * 
   * @return List of researchers.
   */
  public List<Researcher> getResearcherList() {
    return researcherList;
  }

  Map<String, idType> uniqueIdList;  
  
  /**
   * Checks if a given unique ID exists.
   * 
   * @param id Unique ID to look up
   * @param type Type of unique ID.
   * @return true if an object with UniqueID id exists, false if not.
   */
  private boolean checkUniqueId(String id, idType type) {
    if (id == null) {
      // we return true here since we may have a researcher with no organization
      // or something similar
      return true;
    }
    
    String val = id.replace(' ', '_');

    if (this.uniqueIdList.containsKey(val) &&
        this.uniqueIdList.get(val) == type) {
      return true;
    }

    return false;
  }
  
  /**
   * Checks if given id exists, and if not, adds it to the list.
   * 
   * @param id ID value.  Spaces are converted to underscores.
   * @param type Type of unique ID.
   * @return false if id already exists, true if not.  If true is returned, id is
   *         added to list, so a later call would return false.
   */
  private boolean addUniqueId(String id, idType type) {
    // ensure id is not empty (either null string or empty string)
    if (id == null || id == "") {
      return false;
    }
    
    String val = id.replace(' ', '_');
    
    if (this.uniqueIdList.containsKey(val)) {
      // already in set, thus isn't a valid ID
      return false;
    }

    this.uniqueIdList.put(val, type);
    return true;
  }
  
  /**
   * Outputs string if verboseMode is set.
   * 
   * @param info String to write to screen.
   */
  private void outputInfo(String info) {
    if (this.verboseMode) {
//      System.out.println(info);
      errorList.add(info);
    }
  }
  
  /**
   * Checks if the specified Researcher object is valid.
   * 
   * @param item Researcher in question
   * @return true if it is a valid Researcher, false otherwise
   */
  private boolean validResearcher(Researcher item) {
    
    if (item.getUniqueID() != null) {
      // UniqueID is already set, so it was already checked
      this.uniqueIdList.put(item.getUniqueID(), idType.idResearcher);
      return true;
    }

    if (!this.addUniqueId(item.getName(), idType.idResearcher ) ) {
      // ID is a duplicate
      outputInfo("RESEARCHER ID CONFLICT: " + item.getName() );
      return false;
    }
    
    return true;
  }
  
  /**
   * Checks if the specified Collaboration object is valid.
   * 
   * @param item Collaboration in question
   * @return true if it is a valid Collaboration, false otherwise
   */
  private boolean validCollaboration(Collaboration item) {

    BigInteger LOWER_YEAR = new BigInteger("1990");
    BigInteger UPPER_YEAR = new BigInteger("2010");
    
    if (item.getUniqueID() != null) {
      // UniqueID is already set, so it was already checked
      this.uniqueIdList.put(item.getUniqueID(), idType.idCollaboration);
      return true;
    }
    
    if (item.getYears() != null) {
      for (BigInteger year : item.getYears().getYear() ) {
        if (year.compareTo(LOWER_YEAR) == -1 ) {
          outputInfo(item.getName() + ": invalid year " + year.toString() 
              + " is less than " + LOWER_YEAR);
          return false;
        }
        if (year.compareTo(UPPER_YEAR) == 1 ) {
          outputInfo(item.getName() + ": invalid year " + year.toString() 
              + " is greater than " + LOWER_YEAR);
          return false;
        }
      }
    }
    
    if (!this.addUniqueId(item.getName(), idType.idCollaboration ) ) {
      // ID is a duplicate
      outputInfo("COLLABORATION ID CONFLICT: " + item.getName() );
      return false;
    }
    
    return true;
  }
  
  /**
   * Checks if the specified Organization object is valid.
   * 
   * @param item Organization in question
   * @return true if it is a valid Organization, false otherwise
   */
  private boolean validOrganization(Organization item) {

    if (item.getUniqueID() != null) {
      // UniqueID is already set, so it was already checked
      this.uniqueIdList.put(item.getUniqueID(), idType.idOrganization);
      return true;
    }
    
    if (!this.addUniqueId(item.getName(), idType.idOrganization ) ) {
      // ID is a duplicate
      outputInfo("ORGANIZATION ID CONFLICT: " + item.getName() );
      return false;
    }
    
    return true;
  }
  
  /**
   * Checks researchers list and removes invalid entries.
   * 
   * @return Number of entries in all lists that failed semantic checks.
   */
  public int checkResearchers() {
    int count = 0;
    
    List<Researcher> result = new ArrayList<Researcher>();
    
    for (Researcher item : this.researcherList) {
      if (validResearcher(item)) {
        item.setUniqueID(item.getName().replace(' ', '_'));
        result.add(item);
      }
      else {
        count++;
      }
    }

    if (!verboseMode) {
      researcherList = result;
    }
    
    return count;
  }
 
  /**
   * Checks that the links in each list are valid.  This function should not
   * be called until all three lists are processed, since there is no way to
   * check links in one pass.
   * 
   * @return Number of entries that are removed
   */
  public int checkLinks() {
    List<Collaboration> cResult = new ArrayList<Collaboration>();
    List<Organization>  oResult = new ArrayList<Organization>();
    List<Researcher>    rResult = new ArrayList<Researcher>();
    
    int count = 0;
    boolean valid;
    for (Collaboration item : this.collaborationList) {
      valid = true;
  
      for (String link : item.getCollaboratingOrganizations().getCollaboratingOrganization()) {
        boolean validLink = this.checkUniqueId(link, idType.idOrganization);
        if (!validLink) {
          outputInfo("Collaboration " + item.getName() + ": Invalid organization: " + link);
        }
        
        valid = valid & validLink;
      }
      
      if (valid) {
        cResult.add(item);
      }
      else {
        count++;
      }
    }
    
    for (Organization item : this.organizationList) {
      valid = true;
      
      valid = valid & this.checkUniqueId(item.getContact(), idType.idResearcher);
      
      if (item.getAffiliatedResearchers() != null) {
        for (String link : item.getAffiliatedResearchers().getAffiliatedResearcher()) {
          boolean validLink = this.checkUniqueId(link, idType.idResearcher);
          if (!validLink) {
            outputInfo("Organization " + item.getName() + ": Invalid researcher: " + link);
          }
          valid = valid & validLink;
        }
      }
      
      if (valid) {
        oResult.add(item);
      }
      else {
        count++;
      }
    }

    for (Researcher item : this.researcherList) {
      valid = true;
      
      valid = valid & this.checkUniqueId(item.getOrganization(), idType.idOrganization);
      
      if (valid) {
        rResult.add(item);
      }
      else {
        outputInfo("Researcher " + item.getName() + ": Invalid organization: " 
            + item.getOrganization());
        count++;
      }
    }

    if (!verboseMode) {
      collaborationList = cResult;
      organizationList = oResult;
      researcherList = rResult;
    }

    return count;
  }
  
  /**
   * Checks organizations list and removes invalid entries.
   * 
   * @return Number of entries in all lists that failed semantic checks.
   */
  public int checkOrganizations() {
    int count = 0;
    
    List<Organization> result = new ArrayList<Organization>();
    
    for (Organization item : this.organizationList) {
      if (validOrganization(item)) {
        item.setUniqueID(item.getName().replace(' ', '_'));
        result.add(item);
      }
      else {
        count++;
      }
    }
    
    if (!verboseMode) {
      organizationList = result;
    } 
    
    return count;
  }
  
  /**
   * Checks collaborations list and removes invalid entries.
   * 
   * @return Number of entries in all lists that failed semantic checks.
   */
  public int checkCollaborations() {
    int count = 0;
    
    List<Collaboration> result = new ArrayList<Collaboration>();
    
    for (Collaboration item : this.collaborationList) {
      if (validCollaboration(item)) {
        item.setUniqueID(item.getName().replace(' ', '_'));
        result.add(item);
      }
      else {
        count++;
      }
    }
    
    if (!this.verboseMode) {
      collaborationList = result;
    }
    
    return count;
  }
  
  /**
   * Gets a list of semantic errors found from last check.
   * 
   * @return List of strings.
   */
  public List<String> getErrorList() {
    return this.errorList;
  }
  
  /**
   * Checks all lists, updates them, and removes invalid entries.
   * 
   * @return Number of entries in all lists that failed semantic checks.
   */
  public int checkAll() {
    errorList.clear();
    
    int count = 0;
    
    count += checkResearchers();
    count += checkOrganizations();
    count += checkCollaborations();    
    
    count += checkLinks();
    
//    List<String> result = new ArrayList<String>();
//    result.addAll(errorList);
    
//    return result;
    return count;
  }
}
